package com.ruge.spring.cloud.gateway.filter;

import com.ruge.spring.cloud.gateway.base.exception.BaseBusinessException;
import com.ruge.spring.cloud.gateway.config.AppConfig;
import com.ruge.spring.cloud.gateway.dao.AppServerAPIs;
import com.ruge.spring.cloud.gateway.error.ApiGatewayError;
import com.ruge.spring.cloud.gateway.pojo.AppCheckSignResponse;
import com.ruge.spring.cloud.gateway.util.JsonTool;
import com.ruge.spring.cloud.gateway.util.UriMatchUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;

/**
 * @author 嘿丷如歌
 * @version V1.0
 * @Description:
 * @date 2021/4/26 22:03
 */
@Slf4j
@Component
public class CheckAppKeyFilter implements GlobalFilter, Ordered {

    private static final String INTERNAL_URI_PATTERN = "/*/internal/**";

    private static volatile List<String> availablePathPattens;

    @Resource
    private AppConfig appConfig;
    @Resource
    private AppServerAPIs appServerAPIs;

    /**
     * Process the Web request and (optionally) delegate to the next {@code WebFilter}
     * through the given {@link GatewayFilterChain}.
     *
     * @param exchange the current server exchange
     * @param chain    provides a way to delegate to the next filter
     * @return {@code Mono<Void>} to indicate when request processing is complete
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestUrl = exchange.getRequest().getURI().toString();

        // 检查url是否在白名单
        if (appConfig.getAppkeyCheckWhiteList().contains(requestUrl)) {
            // 配置了白名单，且当前请求匹配了白名单
            log.debug("CheckAppKeyFilter: the uri {} matched whiteList", requestUrl);
        } else {
            if (!UriMatchUtil.patternUriMatch(getAvailablePathPattens(), requestUrl)) {
                log.error("invalid requestUrl:{}", requestUrl);
                throw new BaseBusinessException(ApiGatewayError.REQUEST_PATH_INVALID);
            }
            // 获取queryString参数
            Map<String, String> params = new HashMap<>();
            String theQueryString = JsonTool.getBeanToJson(request.getQueryParams());
            if (StringUtils.isBlank(theQueryString)) {
                log.error("invalid theQueryString:{}", theQueryString);
                throw new BaseBusinessException(ApiGatewayError.REQUEST_PARAMETER_INVALID);
            }
            try {
                Map<String, String> tempParams = new HashMap<>();
                String[] param = requestUrl.split("&");
                for (String keyvalue : param) {
                    String[] pair = keyvalue.split("=");
                    if (pair.length == 2) {
                        tempParams.put(pair[0], pair[1]);
                    }
                }
                // 某些业务会出现url参数值为base64的情况（包含等号），会导致之前的Splitter工作出问题，所以在Splitter工作之后再做urlDecode
                tempParams.forEach((key, value) -> {
                    try {
                        if (StringUtils.isNotEmpty(value)) {
                            params.put(key, URLDecoder.decode(value, "UTF-8"));
                        } else {
                            params.put(key, value);
                        }
                    } catch (UnsupportedEncodingException e) {
                        log.error("url参数不是UTF-8编码", e);
                        throw new BaseBusinessException(ApiGatewayError.REQUEST_PARAMETER_INVALID);
                    }
                });
            } catch (Exception e) {
                log.error("Appkey checker error. Invalid queryString: {}", theQueryString, e);
                throw new BaseBusinessException(ApiGatewayError.REQUEST_PARAMETER_INVALID);
            }


            String signt = params.get("signt");
            if (StringUtils.isBlank(signt)) {
                log.warn("-----==== signt parameter lose request uri: {}", requestUrl);
            }

            // appkey、sign参数是否存在校验
            // 网关不做具体业务的检查，交由appserver去校验
            String appkey = params.get("appkey");
            if (StringUtils.isBlank(appkey)) {
                log.error("AppkeyChecker error. appkey parameter is required");
                throw new BaseBusinessException(ApiGatewayError.APPKEY_PARAMETER_NOT_FOUND);
            }

            if (StringUtils.isNotBlank(requestUrl)) {
                requestUrl = requestUrl.substring(1);
            }
            Map<String, String> map = new HashMap<>(2);
            map.put("url", requestUrl);

            AppCheckSignResponse response = appServerAPIs.checkSign(map);
            if (response.getStatus() == AppCheckSignResponse.Status.FAILED) {
                String requestQueryString = requestUrl + ("?" + theQueryString);
                log.debug("checkAppKey req faild: {}", requestQueryString);
                log.error("checkAppKey req faild response; errorCode:{}, errorMsg:{}", response.getErrorCode(), response.getErrorMessage());

                if ("APP.0006".equals(response.getErrorCode())) {
                    throw new BaseBusinessException(ApiGatewayError.SIGNATURE_INVALID);
                }
                throw new BaseBusinessException(ApiGatewayError.SIGNATURE_VALIDATION_FAILED);
            }


        }
        return chain.filter(exchange); //继续向下执行
    }

    /**
     * 获取合法的uri规则
     *
     * @return List</>
     */
    private List<String> getAvailablePathPattens() {
        if (availablePathPattens == null) {
            synchronized (CheckAppKeyFilter.class) {
                availablePathPattens = new ArrayList<>();
                availablePathPattens.add(INTERNAL_URI_PATTERN);
                availablePathPattens.addAll(Arrays.asList(UserTokenCheckFilter.PUBLIC_URI_PATTERN.split(",")));
                availablePathPattens.addAll(Arrays.asList(InPublicFilter.INPUBLIC_URI_PATTERN.split(",")));
                availablePathPattens.addAll(Arrays.asList(AdminTokenCheckFilter.ADMIN_URI_PATTERN.split(",")));
                availablePathPattens.addAll(appConfig.getNonStandardPath().get("admin"));
                availablePathPattens.addAll(appConfig.getNonStandardPath().get("public"));
                availablePathPattens.addAll(appConfig.getNonStandardPath().get("internal"));
                availablePathPattens.addAll(appConfig.getNonStandardPath().get("inpublic"));
            }
        }

        return availablePathPattens;
    }


    @Override
    public int getOrder() {
        return 0;
    }
}
